import UIKit

class SC_Actor_DemoVC: UIViewController {

    private func JuLog(_ items: Any..., separator: String = " ", terminator: String = "\n") {
        let text = items.map { String(describing: $0) }.joined(separator: separator)
        debugPrint(text + terminator)
    }

    private let doc: String = """
# Swift Concurrency 详解（Actors）

## Actor 隔离
- Actor 通过消息序列化保证内部状态的互斥访问，跨隔离调用需要 `await`。

## MainActor 与 Global Actor
- 使用 `@MainActor` 保证 UI 更新在主线程；`global actor` 可为子系统定义统一隔离域。

## nonisolated 与 Sendable
- `nonisolated` 成员无需跨隔离，常用于只读或纯函数。
- `Sendable` 保证跨并发边界的数据结构安全。

## 重入
- 在 actor 隔离内调用可能 `await` 的外部代码会释放隔离，需避免持锁等待导致的竞态。

示例 actor：
- `CounterActor`、`BankAccountActor`、`DownloadManagerActor`、`ThreadSafeArrayActor`
"""

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground

        let textView = UITextView(frame: view.bounds)
        textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        textView.isEditable = false
        textView.text = doc
        textView.font = UIFont.systemFont(ofSize: 14)
        view.addSubview(textView)

        Task { await self.demoCounter() }
        Task { await self.demoBankAccount() }
        Task { await self.demoDownloadManager() }
        Task { await self.demoThreadSafeArray() }
    }

    actor CounterActor {
        private var value: Int = 0
        func increment() { value += 1 }
        func get() -> Int { value }
        nonisolated var description: String { "CounterActor" }
    }

    actor BankAccountActor {
        private var balance: Decimal = 0
        func deposit(_ amount: Decimal) { balance += amount }
        func withdraw(_ amount: Decimal) -> Bool {
            guard balance >= amount else { return false }
            balance -= amount
            return true
        }
        func getBalance() -> Decimal { balance }
    }

    actor DownloadManagerActor {
        private var store: [URL: Data] = [:]
        func startDownload(url: URL) async throws -> Data {
            if let d = store[url] { return d }
            let (data, _) = try await URLSession.shared.data(from: url)
            store[url] = data
            return data
        }
        func downloadAll(urls: [URL]) async throws -> [URL: Data] {
            var result: [URL: Data] = [:]
            try await withThrowingTaskGroup(of: (URL, Data).self) { group in
                for u in urls {
                    group.addTask { [weak self] in
                        guard let self else { throw URLError(.cancelled) }
                        let d = try await self.startDownload(url: u)
                        return (u, d)
                    }
                }
                for try await (u, d) in group { result[u] = d }
            }
            return result
        }
    }

    actor ThreadSafeArrayActor<Element> {
        private var storage: [Element] = []
        func append(_ e: Element) { storage.append(e) }
        func popLast() -> Element? { storage.popLast() }
        func snapshot() -> [Element] { storage }
    }

    @MainActor
    private func updateUI(_ text: String) {
        JuLog("MainActor", text)
        view.backgroundColor = .systemGroupedBackground
    }

    private func demoCounter() async {
        let c = CounterActor()
        await c.increment()
        await c.increment()
        let v = await c.get()
        JuLog("counter", v, c.description)
        await MainActor.run { self.updateUI("counter_done") }
    }

    private func demoBankAccount() async {
        let b = BankAccountActor()
        await b.deposit(100)
        let ok = await b.withdraw(30)
        let bal = await b.getBalance()
        JuLog("bank", ok, bal)
    }

    private func demoDownloadManager() async {
        let m = DownloadManagerActor()
        let urls = [URL(string: "https://jsonplaceholder.typicode.com/todos/1")!, URL(string: "https://jsonplaceholder.typicode.com/todos/2")!]
        do {
            let dict = try await m.downloadAll(urls: urls)
            JuLog("download_all", dict.keys.count)
        } catch {
            JuLog("download_error", error)
        }
    }

    private func demoThreadSafeArray() async {
        let a = ThreadSafeArrayActor<Int>()
        await a.append(1)
        await a.append(2)
        let s = await a.snapshot()
        JuLog("array_snapshot", s)
    }
}